package com.mingyuan.library.domain.lending.patron.model.aggregate;


import com.mingyuan.library.domain.common.event.EitherResult;
import com.mingyuan.library.domain.common.model.Aggregate;
import com.mingyuan.library.domain.lending.book.model.entity.AvailableBook;
import com.mingyuan.library.domain.lending.book.model.entity.BookOnHold;
import com.mingyuan.library.domain.lending.librarybranch.model.valueobject.LibraryBranchId;
import com.mingyuan.library.domain.lending.patron.event.*;
import com.mingyuan.library.domain.lending.patron.model.valueobject.*;
import com.mingyuan.library.domain.lending.patron.strategy.PlacingOnHoldPolicy;
import io.vavr.collection.List;
import io.vavr.control.Either;
import io.vavr.control.Option;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NonNull;

import java.io.Serializable;


/**
 * 读者信息聚合
 *
 * @author yangzk
 * @date 2023/12/28 18:13
 **/
@Aggregate
@AllArgsConstructor
@EqualsAndHashCode(of = "patron")
public class Patron implements Serializable {
    private static final long serialVersionUID = -7096285294411018867L;
    /**
     * 读者信息
     */
    @NonNull
    private final PatronInformation patron;

    @NonNull
    private final List<PlacingOnHoldPolicy> placingOnHoldPolicies;

    @NonNull
    private final OverdueCheckouts overdueCheckouts;
    /**
     * 读者预定
     */
    @NonNull
    private final PatronHolds patronHolds;

    public Either<BookHoldFailed, BookPlacedOnHoldEvents> placeOnHold(AvailableBook book) {
        return placeOnHold(book, HoldDuration.openEnded());
    }

    /**
     * 图书预定
     *
     * @param aBook    可预定的图书
     * @param duration 预定时长
     * @author yangzk
     * @date 2023/12/28 22:43
     **/
    public Either<BookHoldFailed, BookPlacedOnHoldEvents> placeOnHold(AvailableBook aBook, HoldDuration duration) {
        // 校验读者可以预定
        Option<Rejection> rejection = patronCanHold(aBook, duration);
        if (rejection.isEmpty()) {
            BookPlacedOnHold bookPlacedOnHold = BookPlacedOnHold.bookPlacedOnHoldNow(aBook.getBookId(), aBook.type(), aBook.getLibraryBranch(), patron.getPatronId(), duration);
            if (patronHolds.maximumHoldsAfterHolding(aBook)) {
                return EitherResult.announceSuccess(BookPlacedOnHoldEvents.events(bookPlacedOnHold, MaximumNumberOhHoldsReached.now(patron, PatronHolds.MAX_NUMBER_OF_HOLDS)));
            }
            return EitherResult.announceSuccess(BookPlacedOnHoldEvents.events(bookPlacedOnHold));
        }
        return EitherResult.announceFailure(BookHoldFailed.bookHoldFailedNow(rejection.get(), aBook.getBookId(), aBook.getLibraryBranch(), patron));
    }

    /**
     * 取消预定
     *
     * @param book 预定图书信息
     * @author yangzk
     * @date 2023/12/28 22:43
     **/
    public Either<BookHoldCancelingFailed, BookHoldCanceled> cancelHold(BookOnHold book) {
        if (patronHolds.a(book)) {
            // 预定取消
            return EitherResult.announceSuccess(BookHoldCanceled.holdCanceledNow(book.getBookId(), book.getHoldPlacedAt(), patron.getPatronId()));
        }
        return EitherResult.announceFailure(BookHoldCancelingFailed.holdCancelingFailedNow(book.getBookId(), book.getHoldPlacedAt(), patron.getPatronId()));
    }

    /**
     * 借阅
     *
     * @param book     预定的图书
     * @param duration 借阅持续时间
     * @return
     * @author yangzk
     * @date 2023/12/28 18:35
     **/
    public Either<BookCheckingOutFailed, BookCheckedOut> checkOut(BookOnHold book, CheckoutDuration duration) {
        // 检查读者是否预定该图书
        if (patronHolds.a(book)) {
            // 借阅成功
            return EitherResult.announceSuccess(BookCheckedOut.bookCheckedOutNow(book.getBookId(), book.type(), book.getHoldPlacedAt(), patron.getPatronId(), duration));
        }
        // 借阅失败
        return EitherResult.announceFailure(BookCheckingOutFailed.bookCheckingOutFailedNow(Rejection.withReason("book is not on hold by patron"), book.getBookId(), book.getHoldPlacedAt(), patron));
    }

    /**
     * 读者已预定的书
     *
     * @param aBook 可用图书
     * @param forDuration 预定时长
     * @author yangzk
     * @date 2023/12/28 22:44
     **/
    private Option<Rejection> patronCanHold(AvailableBook aBook, HoldDuration forDuration) {
        return placingOnHoldPolicies
                .toStream()
                .map(policy -> policy.apply(aBook, this, forDuration))
                .find(Either::isLeft)
                .map(Either::getLeft);
    }

    /**
     * 是否常规读者
     *
     * @author yangzk
     * @date 2024/1/16 13:31
     **/
    public boolean isRegular() {
        return patron.isRegular();
    }

    /**
     * 逾期借阅数量
     *
     * @param libraryBranch 图书馆分馆ID
     * @author yangzk
     * @date 2024/1/16 13:32
     **/
    public int overdueCheckoutsAt(LibraryBranchId libraryBranch) {
        return overdueCheckouts.countAt(libraryBranch);
    }

    /**
     * 预定数量
     *
     * @author yangzk
     * @date 2024/1/16 13:32
     **/
    public int numberOfHolds() {
        return patronHolds.count();
    }


}


